/*
 * Copyright (c) 2010 Philip Foulkes <philip.foulkes@gmail.com>
 *
 * This file is free software and is made available under the
 * terms of the GNU General Public License, version 2, or at your
 * option, any later version
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#include <linux/slab.h>
#include <linux/hardirq.h>

#include "mrp_private.h"
#include "mrp_attribute.h"
#include "mrp_applicant.h"
#include "mrp_registrar.h"
#include "mrp_state_machine.h"

struct mrp_attribute * mrp_attribute_create_and_init (struct mrp * mrp, int attribute_type, void * attribute_value, int attribute_value_length, int four_packed_type, mrp_attribute_value_compare mrp_attribute_value_compare)
{
	struct mrp_attribute * mrp_attribute = NULL;

	if (!(attribute_type >= MRP_ATTRIBUTE_VALUE_MIN && attribute_type <= MRP_ATTRIBUTE_VALUE_MAX)
		|| attribute_value_length < 0 )
		return NULL;

	if (	!(mrp_attribute = kzalloc(sizeof(struct mrp_attribute), MRP_GET_CONTEXT_ALLOC_FLAG))
		||  !(mrp_attribute->mrp_applicant = mrp_applicant_create_and_init (mrp_attribute))
		|| 	!(mrp_attribute->mrp_registrar = mrp_registrar_create_and_init (mrp_attribute))
		||	!(mrp_attribute->attribute_value = kmalloc (attribute_value_length, MRP_GET_CONTEXT_ALLOC_FLAG)) )
	{
		mrp_attribute_destroy (&mrp_attribute);
		return NULL;
	}

	INIT_LIST_HEAD (&mrp_attribute->list);
	mrp_attribute->mrp = mrp;
	mrp_attribute->attribute_type = attribute_type;
	memcpy (mrp_attribute->attribute_value, attribute_value, attribute_value_length);
	mrp_attribute->attribute_value_length = attribute_value_length;
	mrp_attribute->four_packed_type = four_packed_type;
	mrp_attribute->mrp_attribute_value_compare = mrp_attribute_value_compare;

	mrp_state_machine_receive_protocol_event ((struct mrp_state_machine *) mrp_attribute->mrp_applicant, MRP_PROTO_EVENT_Begin);
	mrp_state_machine_receive_protocol_event ((struct mrp_state_machine *) mrp_attribute->mrp_registrar, MRP_PROTO_EVENT_Begin);

	return mrp_attribute;
}

void mrp_attribute_destroy (struct mrp_attribute ** mrp_attribute)
{
	struct mrp_attribute * mrp_a;

	mrp_a = *mrp_attribute;

	if (mrp_a)
	{
		mrp_registrar_destroy (&mrp_a->mrp_registrar);
		mrp_applicant_destroy (&mrp_a->mrp_applicant);
		kfree (mrp_a->attribute_value);
		kfree (mrp_a);
		*mrp_attribute = NULL;
	}
}

int mrp_attribute_get_attribute_type (struct mrp_attribute * mrp_attribute)
{
	return mrp_attribute->attribute_type;
}

void * mrp_attribute_get_attribute_value (struct mrp_attribute * mrp_attribute)
{
	return mrp_attribute->attribute_value;
}

int mrp_attribute_set_attribute_value (struct mrp_attribute * mrp_attribute, void * attribute_value, int attribute_value_length)
{
	mrp_attribute->attribute_value = attribute_value;
	mrp_attribute->attribute_value_length = attribute_value_length;
	return 0;
}

int mrp_attribute_get_attribute_value_length (struct mrp_attribute * mrp_attribute)
{
	return mrp_attribute->attribute_value_length;
}

int mrp_attribute_get_four_packed_type (struct mrp_attribute * mrp_attribute)
{
	return mrp_attribute->four_packed_type;
}

int mrp_attribute_is_registered (struct mrp_attribute * mrp_attribute)
{
	return mrp_registrar_is_registered (mrp_attribute->mrp_registrar);
}

int mrp_attribute_is_leaving (struct mrp_attribute * mrp_attribute)
{
	return mrp_registrar_is_leaving (mrp_attribute->mrp_registrar);
}

int mrp_attribute_is_empty (struct mrp_attribute * mrp_attribute)
{
	return mrp_registrar_is_empty (mrp_attribute->mrp_registrar);
}

int mrp_attribute_compare (struct mrp_attribute * mrp_attribute, int attribute_type, void * attribute_value)
{
	if (mrp_attribute->mrp_attribute_value_compare == NULL)
		return -EINVAL;

	if (	mrp_attribute->attribute_type == attribute_type
		&&  mrp_attribute->mrp_attribute_value_compare (mrp_attribute->attribute_value, attribute_value) == 0)
	{
		return 0;
	}

	return 1;
}

int mrp_attribute_perform_new (struct mrp_attribute * mrp_attribute)
{
	return mrp_state_machine_receive_protocol_event ((struct mrp_state_machine *)mrp_attribute->mrp_applicant,	MRP_PROTO_EVENT_New);
}

int mrp_attribute_perform_join (struct mrp_attribute * mrp_attribute)
{
	return mrp_state_machine_receive_protocol_event ((struct mrp_state_machine *)mrp_attribute->mrp_applicant, MRP_PROTO_EVENT_Join);
}

int mrp_attribute_perform_lv (struct mrp_attribute * mrp_attribute)
{
	return mrp_state_machine_receive_protocol_event ((struct mrp_state_machine *)mrp_attribute->mrp_applicant, MRP_PROTO_EVENT_Lv);
}

int mrp_attribute_perform_periodic_protocol_action (struct mrp_attribute * mrp_attribute)
{
	return mrp_state_machine_receive_protocol_event ((struct mrp_state_machine *)mrp_attribute->mrp_applicant, MRP_PROTO_ACTION_periodic);
}

int mrp_attribute_perform_receive_new (struct mrp_attribute * mrp_attribute)
{
	return mrp_state_machine_receive_protocol_event ((struct mrp_state_machine *)mrp_attribute->mrp_applicant, MRP_PROTO_EVENT_rNew)
		|| mrp_state_machine_receive_protocol_event ((struct mrp_state_machine *)mrp_attribute->mrp_registrar, MRP_PROTO_EVENT_rNew);
}

int mrp_attribute_perform_receive_join_in (struct mrp_attribute * mrp_attribute)
{
	return mrp_state_machine_receive_protocol_event ((struct mrp_state_machine *)mrp_attribute->mrp_applicant, MRP_PROTO_EVENT_rJoinIn)
		|| mrp_state_machine_receive_protocol_event ((struct mrp_state_machine *)mrp_attribute->mrp_registrar, MRP_PROTO_EVENT_rJoinIn);
}

int mrp_attribute_perform_receive_in (struct mrp_attribute * mrp_attribute)
{
	return mrp_state_machine_receive_protocol_event ((struct mrp_state_machine *)mrp_attribute->mrp_applicant, MRP_PROTO_EVENT_rIn);
}

int mrp_attribute_perform_receive_join_mt (struct mrp_attribute * mrp_attribute)
{
	return mrp_state_machine_receive_protocol_event ((struct mrp_state_machine *)mrp_attribute->mrp_applicant, MRP_PROTO_EVENT_rJoinMt)
		|| mrp_state_machine_receive_protocol_event ((struct mrp_state_machine *)mrp_attribute->mrp_registrar, MRP_PROTO_EVENT_rJoinMt);
}

int mrp_attribute_perform_receive_mt (struct mrp_attribute * mrp_attribute)
{
	return mrp_state_machine_receive_protocol_event ((struct mrp_state_machine *)mrp_attribute->mrp_applicant, MRP_PROTO_EVENT_rMt);
}

int mrp_attribute_perform_receive_lv (struct mrp_attribute * mrp_attribute)
{
	return mrp_state_machine_receive_protocol_event ((struct mrp_state_machine *)mrp_attribute->mrp_applicant, MRP_PROTO_EVENT_rLv)
		|| mrp_state_machine_receive_protocol_event ((struct mrp_state_machine *)mrp_attribute->mrp_registrar, MRP_PROTO_EVENT_rLv);
}

int mrp_attribute_perform_receive_la (struct mrp_attribute * mrp_attribute)
{
	return mrp_state_machine_receive_protocol_event ((struct mrp_state_machine *)mrp_attribute->mrp_applicant, MRP_PROTO_EVENT_rLA)
		|| mrp_state_machine_receive_protocol_event ((struct mrp_state_machine *)mrp_attribute->mrp_registrar, MRP_PROTO_EVENT_rLA);
}

int mrp_attribute_perform_receive_tx_la (struct mrp_attribute * mrp_attribute)
{
	return mrp_state_machine_receive_protocol_event ((struct mrp_state_machine *)mrp_attribute->mrp_registrar, MRP_PROTO_EVENT_txLA);
}

int mrp_attribute_perform_receive_leave_all (struct mrp_attribute * mrp_attribute)
{
	return mrp_state_machine_receive_protocol_event ((struct mrp_state_machine *)mrp_attribute->mrp_applicant, MRP_PROTO_EVENT_rLA)
		|| mrp_state_machine_receive_protocol_event ((struct mrp_state_machine *)mrp_attribute->mrp_registrar, MRP_PROTO_EVENT_rLA);
}

int mrp_attribute_perform_mad_join_indication (struct mrp_attribute * mrp_attribute, int new)
{
	return mrp_perform_mad_join_indication (mrp_attribute->mrp, mrp_attribute->attribute_type, mrp_attribute->attribute_value, mrp_attribute->four_packed_type, new);
}

int mrp_attribute_perform_mad_leave_indication (struct mrp_attribute * mrp_attribute)
{
	return mrp_perform_mad_leave_indication (mrp_attribute->mrp, mrp_attribute->attribute_type, mrp_attribute->attribute_value, mrp_attribute->four_packed_type);
}

int mrp_attribute_perform_send_join (struct mrp_attribute * mrp_attribute)
{
	return mrp_perform_send_join (mrp_attribute->mrp, mrp_attribute);
}

int mrp_attribute_perform_send_new (struct mrp_attribute * mrp_attribute)
{
	return mrp_perform_send_new (mrp_attribute->mrp, mrp_attribute);
}

int mrp_attribute_perform_send_leave (struct mrp_attribute * mrp_attribute)
{
	return mrp_perform_send_leave (mrp_attribute->mrp, mrp_attribute);
}

int mrp_attribute_perform_send (struct mrp_attribute * mrp_attribute)
{
	return mrp_perform_send (mrp_attribute->mrp, mrp_attribute);
}

int mrp_attribute_request_transmit_opportunity (struct mrp_attribute * mrp_attribute, struct mrp_applicant * mrp_applicant)
{
	return mrp_perform_request_transmit_opportunity (mrp_attribute->mrp, (struct mrp_state_machine *) mrp_applicant);
}

int mrp_attribute_registrar_leave_timer_expire_notify (struct mrp_attribute * mrp_attribute)
{
	return mrp_registrar_leave_timer_expire_notify (mrp_attribute->mrp, mrp_attribute);
}
